/* the way boxas align punchies
 */

#include "common/sketchbook.hpp"

constexpr float circle_radius = .12;
constexpr float corner_radius = 14.f;
constexpr float precision_mode_motion_scale = .01f;

constexpr float2 half = float2::one(.5f);

struct line
{
	float2 start;
	float2 direction;
};

struct circle
{
	float2 center;
	float radius;
	float magnitude() const;
	explicit operator range2f() const;
	bool contains(float2 point) const;
};

line l;
circle c;

bool precision_mode = false;
float2* dragged_point = nullptr;
float* dragged_radius = nullptr;
circle* dragged_circle = nullptr;

bool is_near(float2 corner, float2 position);

sketch arrow(sketch s, float2 start, float2 end);

void start(Program& program)
{
	program.key_down = [](scancode code, keycode)
	{
		switch(code)
		{

			case scancode::lshift:
			case scancode::rshift:
				precision_mode = true;
			break;

			default: break;
		}
	};

	program.key_up = [&program](scancode code, keycode)
	{
		switch(code)
		{
			case scancode::leftbracket:
			case scancode::c:
				if(pressed(scancode::rctrl) || pressed(scancode::lctrl))
			case scancode::escape:
				program.end();
			break;

			case scancode::lshift:
			case scancode::rshift:
				precision_mode = false;
			break;

			default: break;
		}
	};

	program.mouse_down = [](float2 position, auto)
	{
		if(is_near(l.start + l.direction, position))
			dragged_point = &l.direction;
		else if(is_near(l.start, position))
			dragged_point = &l.start;
		else if(c.contains(position))
			dragged_circle = &c;
	};

	program.mouse_up = [](auto, auto)
	{
		dragged_point = nullptr;
		dragged_circle = nullptr;
		dragged_radius = nullptr;
	};

	program.mouse_move = [](auto, float2 motion)
	{
		if(precision_mode)
			motion *= precision_mode_motion_scale;
		if(dragged_circle)
			dragged_circle->center += motion;
		if(dragged_point)
			(*dragged_point) += motion;
		if(dragged_radius)
			(*dragged_radius) += motion.x();
	};

	program.draw_once = [](auto frame)
	{
		const float2 center = frame.size / 2;
		c.center = center;
		c.radius = frame.size.x() * circle_radius;
		l.start = center - 2 * c.radius;
		l.direction = float2::one(4 * c.radius);
	};

	program.draw_loop = [](auto frame, auto)
	{
		const float2 center_direction = c.center - l.start;
		bool itchin = center_direction(l.direction) < l.direction.quadrance();

		frame.begin_sketch()
			.rectangle(rect{ frame.size })
			.fill(0xffffff_rgb)
		;

		geom::loop(frame.size/3, [&](auto i) {
			const float2 center_direction = c.center - i*3;
			const auto quadrance = l.direction.quadrance();
			auto normal = common::normalize(common::rotate(l.direction, common::protractor<>::tau(1/4.f)));
			bool itchin = center_direction(l.direction) < quadrance
				&& center_direction(normal) < c.radius
				&& center_direction(-normal) < c.radius
			;

			frame.begin_sketch().ellipse(rect{float2::one(3), i*3}).fill(itchin ? 0xaa0000_rgb : 0xffffff_rgb);
		});

		frame.begin_sketch()
			.ellipse(range2f(c))
			.fill(itchin ? 0x00aa00_rgb : 0xaaaaaa_rgb)
		;

		arrow(frame.begin_sketch(), l.start, l.start + l.direction)
			.line_width(1).outline(0x770077_rgb)
		;

		frame.begin_sketch()
			.ellipse(rect{float2::one(corner_radius), l.start, half})
			.line_width(1).outline(0x555555_rgb)
		;

		const float2 line_point = l.start + l.direction;
		frame.begin_sketch()
			.ellipse(rect{float2::one(corner_radius), line_point, half})
			.line_width(1).outline(0x555555_rgb)
		;

	};
}

float circle::magnitude() const
{
	return radius * radius;
}

circle::operator range2f() const
{
	return {center - radius, center + radius};
}

bool circle::contains(float2 point) const
{
	return (center - point).magnitude() < magnitude();
}

bool is_near(float2 corner, float2 position)
{
	return circle{corner, corner_radius}.contains(position);
}

sketch arrow(sketch s, float2 start, float2 end)
{
	float2 direction = end - start;
	float2 perpendicular = direction.mix<1,0>() * float2(-1.f,1.f);
	float2 shaft = start + direction * .8f;
	s.line(start, end);
	s.line(end, shaft + perpendicular * .1f);
	s.line(end, shaft - perpendicular * .1f);
	return s;
}
